package de.dopichaj.labrador.index.meta;


import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.log4j.Logger;

import de.dopichaj.labrador.index.Tag;
import de.dopichaj.labrador.index.TagFactory;
import de.dopichaj.labrador.search.hit.ChainedHit;
import de.dopichaj.labrador.search.hit.Hit;
import de.dopichaj.labrador.search.hit.InnerChainedHit;
import de.dopichaj.labrador.search.hit.RootChainedHit;
import de.dopichaj.labrador.util.io.ArrayByteReader;
import de.dopichaj.labrador.util.io.BitInputStream;
import de.dopichaj.labrador.util.io.BitOutputStream;
import de.dopichaj.labrador.util.io.ByteReader;
import de.dopichaj.labrador.util.io.ByteWriter;


public final class BaselineDocumentMetaData implements DocumentMetaData {

    private static final Logger log = Logger.getLogger(BaselineDocumentMetaData.class);
    
    private final byte[] bytes;
    private final File file;
    private final int startID;
    private final int elementCount;
    private final TagFactory tagFactory;
    private final BaselineDocumentMetaDataIOFactory ioFactory;
    
    public BaselineDocumentMetaData(final BaselineDocumentMetaDataIOFactory ioFactory,
        final TagFactory tagFactory, ByteReader reader) throws IOException {
        
        this.ioFactory = ioFactory;
        this.tagFactory = tagFactory;
        final BitInputStream input = new BitInputStream(reader);

        final ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
        final BitOutputStream output = new BitOutputStream(new ByteWriter(new DataOutputStream(byteOutput), 1));
        
        this.file = new File(input.readString());
        // output.writeString(file.toString());
        this.startID = input.readVariableLengthInt();
        // output.writeVariableLengthInt(startID);
        this.elementCount = input.readVariableLengthInt();
        // output.writeVariableLengthInt(elementCount);
        log.info("Reading digest for " + file + " (" + elementCount + " elements) " + startID);
        
        final BaselineDocumentMetaDataInput mdInput = ioFactory.getInput(input);
        final BaselineDocumentMetaDataOutput mdOutput = ioFactory.getOutput(output);
        mdOutput.writeTagID(mdInput.readTagID());
        mdOutput.writeLengthDelta(mdInput.readLengthDelta());
        for (int i = 1; i < elementCount; i++) {
            mdOutput.writeIDDelta(mdInput.readIDDelta());
            mdOutput.writeTagID(mdInput.readTagID());
            mdOutput.writePositionDelta(mdInput.readPositionDelta());
            mdOutput.writeLengthDelta(mdInput.readLengthDelta());
        }
        input.skipToNext();
        output.close();
        this.bytes = byteOutput.toByteArray();
    }

    /**
     * Get the {@link BitInputStream} for the bytes.
     */
    private BitInputStream getBitInputStream() {
        return new BitInputStream(new ArrayByteReader(bytes));
    }
    
    public int getEndId() {
        return startID + elementCount;
    }

    public File getFile() {
        return file;
    }

    private static final class HitData {
        public final int tagID;
        public int length;
        public final int position;
        public final HitData parent;
        public final int id;
        public final int xPathPosition;
        public ChainedHit hit;
        public HitData(final int id, final HitData parent, final int tagID,
            final int length, final int position, final int xPathPosition) {
            
            this.id = id;
            this.parent = parent;
            this.tagID = tagID;
            this.length = length;
            this.position = position;
            this.xPathPosition = xPathPosition;
        }
    }
    
    public Map<Integer, Hit> getFragments() {
        return getFragments(null);
    }

    private ChainedHit makeHit(final HitData hitData) {
        
        // if this is the root hit, create the root hit
        final Tag tag = tagFactory.getTag(hitData.tagID);
        if (hitData.parent == null) {

            hitData.hit = new RootChainedHit(file, hitData.length, tag);
            
        // else create the chained hit
        } else {
        
            // make the hit
            hitData.hit = new InnerChainedHit(hitData.parent.hit, hitData.length,
                hitData.position, tag, hitData.xPathPosition);
        }
        return hitData.hit;
    }
    
    /**
     * Add the hit and its ancestors to the result.
     */
    private void addHitAndAncestors(final Map<Integer, Hit> result,
        final HitData hitData) {
        
        // add the parent, if necessary
        if (hitData.parent != null && hitData.parent.hit == null) {
            
            addHitAndAncestors(result, hitData.parent);
        }
        
        // add this hit
        result.put(hitData.id, makeHit(hitData));
    }
    
    public Map<Integer, Hit> getFragments(final List<Integer> wantedIDs) {
        
        final BaselineDocumentMetaDataInput input = ioFactory.getInput(getBitInputStream());

        if (elementCount > 0) {
     
            // read the root element
            final ArrayList<HitData> hitData = new ArrayList<HitData>(elementCount);
            try {
                // skipHeader(input);

                hitData.add(new HitData(startID, null, input.readTagID(),
                    input.readLengthDelta(), 0, 1));
            } catch (IOException e) {
                log.error("Error reading metadata for " + file + ", id 0", e);
                return Collections.emptyMap();
            }
            
            // for all remaining elements
            for (int id = 1; id < elementCount; id++) {
                
                // ... read the new data
                final int parentID;
                final int tagID;
                final int positionDelta;
                final int lengthDelta;
                try {
                    parentID = id - input.readIDDelta();
                    assert parentID >= 0;
                    tagID = input.readTagID();
                    positionDelta = input.readPositionDelta();
                    lengthDelta = input.readLengthDelta();
                } catch (IOException e) {
                    log.error("Error reading metadata for " + file + ", id " + id, e);
                    return Collections.emptyMap();
                }
                
                // ... determine the position of this XPath part
                final HitData parent = hitData.get(parentID);
                int xPathPosition = 1;
                for (int siblingID = parentID + 1; siblingID < id; siblingID++) {
                    final HitData possibleSibling = hitData.get(siblingID);
                    if (possibleSibling.parent == parent && possibleSibling.tagID == tagID) {
                        xPathPosition++;
                    }
                }
                
                // ... add the parent's data
                final int position = positionDelta + parent.position;
                final int length = lengthDelta;
                hitData.add(new HitData(startID + id, parent, tagID,
                    length, position, xPathPosition));
                
                // update the ancestors' lengths
                for (HitData ancestor = parent; ancestor != null; ancestor = ancestor.parent) {
                    ancestor.length += length;
                }
            }

            // create the hits
            final Map<Integer, Hit> result = new TreeMap<Integer, Hit>();
            
            if (wantedIDs == null) {
                for (int id = 0; id < elementCount; id++) {
                    addHitAndAncestors(result, hitData.get(id));
                }
            } else {
                
                for (final int id : wantedIDs) {
                    addHitAndAncestors(result, hitData.get(id - startID));
                }
            }
            return result;
        } else {
            return Collections.emptyMap();
        }
    }

    public int getStartId() {
        return startID;
    }

}
/*
Copyright (c) 2007 Philipp Dopichaj

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/